package pl.droidsonroids.gif;

import java.lang.ref.WeakReference;
import java.util.concurrent.CopyOnWriteArrayList;

import ohos.agp.components.Component;
import ohos.agp.components.element.Element;

/**
 * {@link Callback} which allows single {@link Drawable} to be associated with multiple callbacks,
 * eg. with multiple {@link View}s.
 * If drawable needs to be redrawn all currently associated callbacks are invoked.
 * If Callback is a View it is then invalidated.
 *
 * @author koral--
 * @author Doctoror
 */
public class MultiCallback implements ElementCallback {
    private final CopyOnWriteArrayList<CallbackWeakReference> mCallbacks = new CopyOnWriteArrayList<>();
    private final boolean mUseViewInvalidate;

    /**
     * Equivalent to {@link #//MultiCallback(boolean)} with <code>false</code> value.
     */
    public MultiCallback() {
        this(false);
    }

    /**
     * Set <code>useViewInvalidate</code> to <code>true</code> if displayed {@link //Drawable} is not supported by
     * {@link// Callback#invalidateDrawable(Drawable)} of the target. For example if it is located inside ImageSpan
     * displayed in Text.
     *
     * @param useViewInvalidate whether {@link //View#invalidate()} should be used instead of
     * {@link //Callback#invalidateDrawable(Drawable)}
     */
    public MultiCallback(final boolean useViewInvalidate) {
        mUseViewInvalidate = useViewInvalidate;
    }

    @Override
    public void invalidateDrawable(final Element who) {
        for (int i = 0; i < mCallbacks.size(); i++) {
            final CallbackWeakReference reference = mCallbacks.get(i);
            final ElementCallback callback = reference.get();
            if (callback != null) {
                if (mUseViewInvalidate && callback instanceof Component) {
                    ((Component) callback).invalidate();
                } else {
                    callback.invalidateDrawable(who);
                }
            } else {
                // Always remove null references to reduce list size
                mCallbacks.remove(reference);
            }
        }
    }

    @Override
    public void scheduleDrawable(final Element who, final Runnable what, final long when) {
        for (int i = 0; i < mCallbacks.size(); i++) {
            final CallbackWeakReference reference = mCallbacks.get(i);
            final ElementCallback callback = reference.get();
            if (callback != null) {
                callback.scheduleDrawable(who, what, when);
            } else {
                // Always remove null references to reduce Set size
                mCallbacks.remove(reference);
            }
        }
    }

    @Override
    public void unScheduleDrawable(final Element who, final Runnable what) {
        for (int i = 0; i < mCallbacks.size(); i++) {
            final CallbackWeakReference reference = mCallbacks.get(i);
            final ElementCallback callback = reference.get();
            if (callback != null) {
                callback.unScheduleDrawable(who, what);
            } else {
                // Always remove null references to reduce list size
                mCallbacks.remove(reference);
            }
        }
    }

    /**
     * Associates given {@link //Callback}. If callback has been already added, nothing happens.
     *
     * @param callback Callback to be associated
     */
    public void addView(final ElementCallback callback) {
        for (int i = 0; i < mCallbacks.size(); i++) {
            final CallbackWeakReference reference = mCallbacks.get(i);
            final ElementCallback item = reference.get();
            if (item == null) {
                // Always remove null references to reduce list size
                mCallbacks.remove(reference);
            }
        }
        mCallbacks.addIfAbsent(new CallbackWeakReference(callback));
    }

    /**
     * Disassociates given {@link //Callback}. If callback is not associated, nothing happens.
     *
     * @param callback Callback to be disassociated
     */
    public void removeView(final ElementCallback callback) {
        for (int i = 0; i < mCallbacks.size(); i++) {
            final CallbackWeakReference reference = mCallbacks.get(i);
            final ElementCallback item = reference.get();
            if (item == null || item == callback) {
                // Always remove null references to reduce list size
                mCallbacks.remove(reference);
            }
        }
    }

    static final class CallbackWeakReference extends WeakReference<ElementCallback> {
        CallbackWeakReference(final ElementCallback r) {
            super(r);
        }

        @Override
        public boolean equals(final Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }

            return get() == ((CallbackWeakReference) o).get();
        }

        @Override
        public int hashCode() {
            final ElementCallback callback = get();
            return callback != null ? callback.hashCode() : 0;
        }
    }
}

